//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/parser/pAST.h"
#include "sagittarius/io/io.h"
#include <string.h>
#include <malloc.h>

void parserInd(int fd, uint64 ind) {
    uint64 i;
    for(i=0;i<ind;++i)
        SG_WRITE(fd, " ");
}

void dumpPAST_type(int fd, parserExpType t, uint64 ind) {
    switch(t) {
        case PAST_NODE_TYPE_SRC:
            SG_WRITE(fd, "PAST_NODE_TYPE_SRC");
            return;
        case PAST_NODE_TYPE_FUNDEF:
            SG_WRITE(fd, "PAST_NODE_TYPE_FUNDEF");
            return;
        case PAST_NODE_TYPE_UINT:
            SG_WRITE(fd, "PAST_NODE_TYPE_UINT");
            return;
        case PAST_NODE_TYPE_REAL:
            SG_WRITE(fd, "PAST_NODE_TYPE_REAL");
            return;
        case PAST_NODE_TYPE_SYMBOL:
            SG_WRITE(fd, "PAST_NODE_TYPE_SYMBOL");
            return;
        case PAST_NODE_TYPE_FACCESS:
            SG_WRITE(fd, "PAST_NODE_TYPE_FACCESS");
            return;
        case PAST_NODE_TYPE_RVALUE:
            SG_WRITE(fd, "PAST_NODE_TYPE_RVALUE");
            return;
        case PAST_NODE_TYPE_RVAL_LINK:
            SG_WRITE(fd, "PAST_NODE_TYPE_RVAL_LINK");
            return;
        case PAST_NODE_TYPE_BBLOCK_LINK:
            SG_WRITE(fd, "PAST_NODE_TYPE_BBLOCK_LINK");
            return;
        case PAST_NODE_TYPE_ASSN:
            SG_WRITE(fd, "PAST_NODE_TYPE_ASSN");
            return;
        case PAST_NODE_TYPE_CALL:
            SG_WRITE(fd, "PAST_NODE_TYPE_CALL");
            return;
        case PAST_NODE_TYPE_IMPORT:
            SG_WRITE(fd, "PAST_NODE_TYPE_IMPORT");
            return;
        case PAST_NODE_TYPE_NETWORK:
            SG_WRITE(fd, "PAST_NODE_TYPE_NETWORK");
            return;
        case PAST_NODE_TYPE_RXN_RULE_LINK:
            SG_WRITE(fd, "PAST_NODE_TYPE_RXN_RULE_LINK");
            return;
        case PAST_NODE_TYPE_RXN_RULE:
            SG_WRITE(fd, "PAST_NODE_TYPE_RXN_RULE");
            return;
        case PAST_NODE_TYPE_RXN:
            SG_WRITE(fd, "PAST_NODE_TYPE_RXN");
            return;
        case PAST_NODE_TYPE_ADD:
            SG_WRITE(fd, "PAST_NODE_TYPE_ADD");
            return;
        case PAST_NODE_TYPE_SUB:
            SG_WRITE(fd, "PAST_NODE_TYPE_SUB");
            return;
        case PAST_NODE_TYPE_MUL:
            SG_WRITE(fd, "PAST_NODE_TYPE_MUL");
            return;
        case PAST_NODE_TYPE_DIV:
            SG_WRITE(fd, "PAST_NODE_TYPE_DIV");
            return;
        case PAST_NODE_TYPE_EXP:
            SG_WRITE(fd, "PAST_NODE_TYPE_EXP");
            return;
        case PAST_NODE_TYPE_NEG:
            SG_WRITE(fd, "PAST_NODE_TYPE_NEG");
            return;
        default:
            AN(0, "Unrecognized type");
    }
}

/// createParserExp
parserExp* createParserExp() {
    parserExp* e=malloc(sizeof(parserExp));
    e->a = NULL;
    e->b = NULL;
    e->ln = -1;
    return e;
}

/// destroyParserExp
void destroyParserExp(parserExp* p) {
    if(p->a && p->owna)
        destroyParserExp(p->a);
    if(p->b && p->ownb)
        destroyParserExp(p->b);
    free(p);
}

sg_OpPrec sg_getOpPrec(parserExpType t) {
    switch(t) {
        case PAST_NODE_TYPE_ADD:
        case PAST_NODE_TYPE_SUB:
            return SG_OP_ADDSUB;
        case PAST_NODE_TYPE_MUL:
        case PAST_NODE_TYPE_DIV:
            return SG_OP_DIVMUL;
        case PAST_NODE_TYPE_EXP:
        case PAST_NODE_TYPE_NEG:
            return SG_OP_EXP;
        default:
            return SG_OP_LEAST;
    }
}

/// dumpParserExp
void dumpParserExp(int fd, parserExp* e, uint64 ind, symtab* st, sg_OpPrec prec) {
    switch(e->type) {
        case PAST_NODE_TYPE_ASSN:
            dumpParserExp(fd, e->a, ind, st, prec);
            SG_WRITE(fd, " = ");
            dumpParserExp(fd, e->b, ind, st, prec);
            SG_WRITE(fd, "");
            return;
        case PAST_NODE_TYPE_CALL:
//             SG_WRITE(fd, "CALL_");
            AN(e->a, "Missing object");
            dumpParserExp(fd, e->a, ind, st, prec);
            SG_WRITE(fd, "(");
            if(e->b) {
                dumpParserExp(fd, e->b, ind, st, prec);
            }
            SG_WRITE(fd, ")");
            return;
        case PAST_NODE_TYPE_FACCESS:
            AN(e->a, "Missing object");
            dumpParserExp(fd, e->a, ind, st, prec);
            SG_WRITE(fd, ".");
            symtabPrintSymK(fd, st, e->lvalid);
            return;
        case PAST_NODE_TYPE_SYMBOL:
//             SG_WRITE(fd, "SYM_");
            symtabPrintSymK(fd, st, e->lvalid);
            return;
        case PAST_NODE_TYPE_RVAL_LINK:
            // rval
            dumpParserExp(fd, e->a, ind, st, prec);
            // next
            if(e->b) {
                SG_WRITE(fd, ",");
                dumpParserExp(fd, e->b, ind, st, prec);
            }
            return;
        case PAST_NODE_TYPE_BBLOCK_LINK:
            AN(e->a, "Missing link");
            parserInd(fd, ind);
            dumpParserExp(fd, e->a, ind, st, prec);
            SG_WRITE(fd, "\n");
            // next
            if(e->b) {
                AT(e->b->type == PAST_NODE_TYPE_BBLOCK_LINK, "Expected basic body link");
                dumpParserExp(fd, e->b, ind, st, prec);
            }
            return;
        case PAST_NODE_TYPE_UINT:
            sg_writeUInt64(fd, e->v.u);
            return;
        case PAST_NODE_TYPE_STR:
            SG_WRITE(fd, "'");
            LX_WRITE_BUFL(fd, e->v.str.w, e->v.str.l);
            SG_WRITE(fd, "'");
            return;
        case PAST_NODE_TYPE_IMPORT:
            dumpImportStmt(fd, e->v.imp, ind, st);
            return;
        case PAST_NODE_TYPE_NETWORK:
            SG_WRITE(fd, "network(");
            if(e->a) {
                AT(e->a->type == PAST_NODE_TYPE_RXN_RULE_LINK, "Expected rule link");
                dumpParserExp(fd, e->a, ind, st, prec);
            }
            SG_WRITE(fd, ")\n");
            return;
        case PAST_NODE_TYPE_RXN_RULE_LINK:
            AN(e->a, "Empty link");
            dumpParserExp(fd, e->a, ind, st, prec);
            if(e->b) {
                AT(e->b->type == PAST_NODE_TYPE_RXN_RULE_LINK, "Expected rule link");
                SG_WRITE(fd, "; ");
                dumpParserExp(fd, e->b, ind, st, prec);
            }
            return;
        case PAST_NODE_TYPE_RXN_RULE:
            AN(e->a, "Empty rxn");
            dumpParserExp(fd, e->a, ind, st, prec);
            SG_WRITE(fd, ", ");
            AN(e->b, "Empty rate");
            dumpParserExp(fd, e->b, ind, st, prec);
            return;
        case PAST_NODE_TYPE_RXN:
            AN(e->a, "Empty lhs");
            dumpParserExp(fd, e->a, ind, st, prec);
            SG_WRITE(fd, " → ");
            AN(e->b, "Empty rhs");
            dumpParserExp(fd, e->b, ind, st, prec);
            return;
        case PAST_NODE_TYPE_ADD:
            if(SG_OP_ADDSUB < prec)
                SG_WRITE(fd, "(");
            AN(e->a, "Empty lhs");
            dumpParserExp(fd, e->a, ind, st, SG_OP_ADDSUB);
            SG_WRITE(fd, " + ");
            AN(e->b, "Empty rhs");
            dumpParserExp(fd, e->b, ind, st, SG_OP_ADDSUB);
            if(SG_OP_ADDSUB < prec)
                SG_WRITE(fd, ")");
            return;
        case PAST_NODE_TYPE_SUB:
            if(SG_OP_ADDSUB < prec)
                SG_WRITE(fd, "(");
            AN(e->a, "Empty lhs");
            dumpParserExp(fd, e->a, ind, st, SG_OP_ADDSUB);
            SG_WRITE(fd, " - ");
            AN(e->b, "Empty rhs");
            dumpParserExp(fd, e->b, ind, st, SG_OP_ADDSUB);
            if(SG_OP_ADDSUB < prec)
                SG_WRITE(fd, ")");
            return;
        case PAST_NODE_TYPE_MUL:
            if(SG_OP_DIVMUL < prec)
                SG_WRITE(fd, "(");
            AN(e->a, "Empty lhs");
            dumpParserExp(fd, e->a, ind, st, SG_OP_DIVMUL);
            SG_WRITE(fd, "*");
            AN(e->b, "Empty rhs");
            dumpParserExp(fd, e->b, ind, st, SG_OP_DIVMUL);
            if(SG_OP_DIVMUL < prec)
                SG_WRITE(fd, ")");
            return;
        case PAST_NODE_TYPE_DIV:
            if(SG_OP_DIVMUL < prec)
                SG_WRITE(fd, "(");
            AN(e->a, "Empty lhs");
            dumpParserExp(fd, e->a, ind, st, SG_OP_DIVMUL);
            SG_WRITE(fd, "/");
            AN(e->b, "Empty rhs");
            dumpParserExp(fd, e->b, ind, st, SG_OP_DIVMUL);
            if(SG_OP_DIVMUL < prec)
                SG_WRITE(fd, ")");
            return;
        default:
            AN(0, "Unknown expression");
    }
}

/// rlinkLen
int64 rlinkLen(parserExp* e) {
    AT(e->type == PAST_NODE_TYPE_RVAL_LINK, "Must have rlink");
    if(e->b)
        return rlinkLen(e->b)+1;
    else
        return 1;
}

/// getNumCallExpArgs
int64 getNumCallExpArgs(parserExp* e) {
    if(e->type != PAST_NODE_TYPE_CALL) {
        AN(0, "Must pass a call exp");
        return -1;
    }
    if(e->b)
        return rlinkLen(e->b);
    else
        return 0;
}

/// getTerminalField
symcode getTerminalField(parserExp* e) {
    switch(e->type) {
        case PAST_NODE_TYPE_FACCESS:
            AN(e->a, "Broken faccess link");
            return getTerminalField(e->a);
        case PAST_NODE_TYPE_SYMBOL:
            return e->lvalid;
        default:
            AN(0, "Expected an faccess chain");
    }
}

symcode exprGetSymbol(parserExp* e) {
    switch(e->type) {
        case PAST_NODE_TYPE_SYMBOL:
            return e->lvalid;
        default:
            AN(0, "Expected a symbol");
    }
}

/// createBasicBody
basicBody* createBasicBody() {
    basicBody* b = malloc(sizeof(basicBody));
    b->n = 0;
    b->e = NULL;
    return b;
}

/// destroyBasicBody
void destroyBasicBody(basicBody* b) {
    if(b->e) {
        uint32 i;
        for(i=0; i<b->n; ++i) {
            destroyParserExp(b->e[i]);
        }
        free(b->e);
    }
    free(b);
}

/// basicBodyGrow
void basicBodyGrow(basicBody* b) {
    if(b->e) {
        parserExp** e = malloc((b->n+1)*sizeof(parserExp*));
        memcpy(e, b->e, b->n*sizeof(parserExp*));
        free(b->e);
        b->e = e;
    } else {
        b->n = 0;
        b->e = malloc(sizeof(parserExp*));
    }
}

/// basicBodyPushExp
void basicBodyPushExp(basicBody* b, parserExp* e) {
    basicBodyGrow(b);
    b->e[b->n++] = e;
}

/// dumpBasicBody
void dumpBasicBody(int fd, basicBody* b, uint64 ind, symtab* st) {
    uint64 i;
    for(i=0; i<b->n; ++i) {
        parserInd(fd, ind);
        SG_WRITE(fd, "Expression: ");
        dumpParserExp(fd, b->e[i], ind, st, SG_OP_LEAST);
        //SG_WRITE(fd, "\n");
    }
}

/// dumpParamListElt
void dumpParamListElt(int fd, paramListElt e, uint64 ind, symtab* st) {
    if(e.ref)
        SG_WRITE(fd, "ref ");
    symtabPrintSym(fd, st, e.sym);
}

/// createParamList
paramList* createParamList() {
    paramList* l = malloc(sizeof(paramList));
    l->p = NULL;
    return l;
}

/// destroyParamList
void destroyParamList(paramList* l) {
    if(l->p)
        free(l->p);
}

/// paramListPush
void paramListPush(paramList* l, paramListElt e) {
    if(l->p) {
        paramListElt* tmp=malloc((l->n+1)*sizeof(paramListElt));
        memcpy(tmp, l->p, l->n*sizeof(paramListElt));
        if(l->p)
            free(l->p);
        l->p = tmp;
        l->p[l->n++] = e;
    } else {
        l->n = 0;
        l->p=malloc((l->n+1)*sizeof(paramListElt));
        l->p[l->n++] = e;
    }
}

/// dumpParamList
void dumpParamList(int fd, paramList* l, uint64 ind, symtab* st) {
    size_t i;
    parserInd(fd, ind);
    SG_WRITE(fd, "Parameters: ");
    for(i=0; i<l->n; ++i) {
        if(i!=0)
            SG_WRITE(fd, ", ");
        dumpParamListElt(fd, l->p[i], ind+2, st);
    }
    SG_WRITE(fd, "\n");
}

/// createFunctionDef
functionDef* createFunctionDef() {
    functionDef* f=malloc(sizeof(functionDef));
    f->pl = NULL;
    return f;
}

/// destroyFunctionDef
void destroyFunctionDef(functionDef* f) {
    if(f->pl)
        destroyParamList(f->pl);
    free(f->pl);
    if(f->b)
        destroyParserExp(f->b);
    free(f);
}

/// dumpFunctionDef
void dumpFunctionDef(int fd, functionDef* f, uint64 ind, symtab* st) {
    parserInd(fd, ind);
    //SG_WRITE(fd, "Function ");
    symtabPrintSymK(fd, st, f->id);
    SG_WRITE(fd, "\n");
    if(f->pl) {
        dumpParamList(fd, f->pl, ind+2, st);
    }
    if(f->b)
        dumpParserExp(fd, f->b, ind, st, SG_OP_LEAST);
}

/// createSingleImport
singleImport* createSingleImport() {
    singleImport* p=malloc(sizeof(singleImport));
    p->exp = NULL;
    return p;
}

/// destroyImportStmt
void destroySingleImport(singleImport* p) {
    free(p);
}

/// dumpSingleImport
void dumpSingleImport(int fd, singleImport* p, uint64 ind, symtab* st) {
    //uint64 i;
    //parserInd(fd, ind);
    dumpParserExp(fd, p->exp, ind, st, SG_OP_LEAST);
    SG_WRITE(fd, " as ");
    symtabPrintSymK(fd, st, p->sym);
}

/// createImportStmt
importStmt* createImportStmt() {
    importStmt* p=malloc(sizeof(importStmt));
    p->p = NULL;
    p->n = 0;
    p->c = 0;
    return p;
}

/// destroyImportStmt
void destroyImportStmt(importStmt* p) {
    if(p->p) {
        uint64 i;
        for(i=0;i<p->n;++i)
            destroySingleImport(p->p[i]);
    }
    free(p->p);
    free(p);
}

void importStmtGrow(importStmt* p) {
    if(p->p) {
        singleImport** s=malloc((p->c<<=1)*sizeof(singleImport*));
        memcpy(s, p->p, p->n*sizeof(singleImport*));
        free(p->p);
        p->p = s;
    } else {
        p->c = 2;
        p->p = malloc((p->c)*sizeof(singleImport*));;
    }
}

/// importStmtPushSingle
void importStmtPushSingle(importStmt* p, singleImport* s) {
    if(p->n == p->c)
        importStmtGrow(p);
    p->p[p->n++] = s;
}

/// dumpImportStmt
void dumpImportStmt(int fd, importStmt* p, uint64 ind, symtab* st) {
    uint64 i;
    //parserInd(fd, ind);
    SG_WRITE(fd, "import ");
    for(i=0;i<p->n;++i)
        dumpSingleImport(fd, p->p[i], ind, st);
}

/// createSourceFrag
sourceFrag* createSourceFrag() {
    sourceFrag* sf = malloc(sizeof(sourceFrag));
    sf->f = NULL;
    sf->b = NULL;
    return sf;
}

/// destroySourceFrag
void destroySourceFrag(sourceFrag* sf) {
    size_t i;
    if(sf->f) {
        for(i=0; i<sf->nf; ++i) {
            destroyFunctionDef(sf->f[i]);
        }
        free(sf->f);
    }
    if(sf->b)
        destroyParserExp(sf->b);
    free(sf);
}

/// sfPushFDef
void sfPushFDef(sourceFrag* sf, functionDef* f) {
    if(sf->f) {
        functionDef **tmp=malloc((sf->nf+1)*sizeof(functionDef*));
        memcpy(tmp, sf->f, sf->nf*sizeof(functionDef*));
        free(sf->f);
        sf->f = tmp;
        sf->f[sf->nf++] = f;
    } else {
        sf->nf = 0;
        sf->f = malloc((sf->nf+1)*sizeof(functionDef*));
        sf->f[sf->nf++] = f;
    }
}

/// sfPushImport
void sfPushImport(sourceFrag* sf, importStmt* f) {
    if(sf->imp) {
        importStmt **tmp=malloc((sf->nimp+1)*sizeof(importStmt*));
        memcpy(tmp, sf->imp, sf->nimp*sizeof(importStmt*));
        free(sf->imp);
        sf->imp = tmp;
        sf->imp[sf->nimp++] = f;
    } else {
        sf->nimp = 0;
        sf->imp = malloc((sf->nimp+1)*sizeof(importStmt*));
        sf->imp[sf->nimp++] = f;
    }
}

/// dumpSourceFrag
void dumpSourceFrag(int fd, sourceFrag* sf, uint64 ind, symtab* st) {
    size_t i;
    parserInd(fd, ind);
    SG_WRITE(fd, "Source fragment\n");
    parserInd(fd, ind);
    if(sf->f) {
        SG_WRITE(fd, "Functions: ");
        sg_writeUInt64(fd, sf->nf);
        SG_WRITE(fd, "\n");
        for(i=0; i<sf->nf; ++i) {
            dumpFunctionDef(fd, sf->f[i], ind+2, st);
        }
    } else {
        SG_WRITE(fd, "No functions\n");
    }
    SG_WRITE(fd, "Body: \n");
    dumpParserExp(fd, sf->b, ind+2, st, SG_OP_LEAST);
}